<!DOCTYPE html>
<!-- saved from url=(0095)https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling?display=Print -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Custom Controls in Win32 API: Scrolling - CodeProject</title> 
	<link type="text/css" rel="stylesheet" href="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/Main.min.css">

	
<meta http-equiv="content-language" content="en-US">

<meta name="Description" content="How to support scrolling within your controls.; Author: Martin Mitáš; Updated: 26 Feb 2018; Section: Windows API; Chapter: Platforms, Frameworks &amp; Libraries; Updated: 26 Feb 2018">
<meta name="Keywords" content="C, Windows, Win32, Win64, Visual-Studio, Dev, custom-controls, VS2013,Windows API,Platforms, Frameworks &amp; Libraries,Free source code, tutorials">
<meta name="Author" content="Martin Mitáš">
<meta name="Rating" content="General">
<meta name="Revisit-After" content="1 days">
<meta name="application-name" content="CodeProject">
<meta name="google-translate-customization" content="d908bb7ce7aff658-4c2f3a504525c916-g629383f736781a8a-13">

<link rel="dns-prefetch" href="https://ajax.googleapis.com/"> 
<link rel="canonical" href="https://www.codeproject.com/Articles/1042516/%2fArticles%2f1042516%2fCustom-Controls-in-Win-API-Scrolling">
<link rel="alternate" type="application/rss+xml" title="CodeProject Latest articles - All Topics" href="https://www.codeproject.com/WebServices/ArticleRSS.aspx?cat=1">
<link rel="alternate" type="application/rss+xml" title="CodeProject Latest articles - Artificial Intelligence" href="https://www.codeproject.com/WebServices/ArticleRSS.aspx?cat=31">
<link rel="alternate" type="application/rss+xml" title="CodeProject Lounge Postings" href="https://www.codeproject.com/webservices/LoungeRSS.aspx">
<meta name="robots" content="index, follow">
<link rel="search" type="application/opensearchdescription+xml" title="CodeProject" href="https://www.codeproject.com/info/OpenSearch.xml">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<link rel="apple-touch-icon" sizes="144x144" href="https://www.codeproject.com/favicon/apple-touch-icon.png"> 
<link rel="icon" type="image/png" sizes="32x32" href="https://www.codeproject.com/favicon/favicon-32x32.png"> 
<link rel="icon" type="image/png" sizes="16x16" href="https://www.codeproject.com/favicon/favicon-16x16.png"> 
<link rel="manifest" href="https://www.codeproject.com/favicon/manifest.json"> 
<link rel="mask-icon" href="https://www.codeproject.com/favicon/safari-pinned-tab.svg" color="#ff9900">
	
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "TechArticle",
  "headline": "Custom Controls in Win32 API: Scrolling",
  "url": "https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling",
  "discussionUrl": "https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling",
  "isFamilyFriendly": "true",
  "image": "https://www.codeproject.com/KB/vista/1042516/Thumbnail.png",
  "keywords": "C, Windows, Win32, Win64, Visual-Studio (VS2013), custom-controls",
  "commentCount": "14",
  "editor" : {
    "@type" : "Person",
    "name" : "Martin Mitáš",
    "url" : "https://www.codeproject.com/script/Membership/View.aspx?mid=5807385"
  },
  "license": "http://www.codeproject.com/info/cpol10.aspx",
  "publisher" : {
    "@type" : "Organization",
    "name" : "CodeProject"
  }
  "description": "How to support scrolling within your controls.",
  "upvoteCount": "50",
  "articleSection": "Windows API",
  "author" : [{
      "@type" : "Person",
      "name" : "Martin Mitáš",
      "url" : "https://www.codeproject.com/script/Membership/View.aspx?mid=5807385"
    }],
  "datePublished": "2015-11-03",
  "dateCreated": "2015-11-03",
  "dateModified": "2018-02-26",
  "aggregateRating" : {
    "@type" : "aggregateRating",
    "ratingValue" : 4.97,
    "ratingCount" : 51,
    "bestRating" : 5,
    "worstRating" : 1
  },
}
</script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [{
    "@type": "ListItem",
    "position": 1,
    "item" : {
      "@id" : "/Chapters/8/Platforms-Frameworks-Libraries.aspx",
      "name" : "Platforms, Frameworks & Libraries"
    }
  },{
    "@type": "ListItem",
    "position": 2,
    "item" : {
      "@id" : "/KB/vista/",
      "name" : "Windows API"
    }
  }]
}
</script>
	<!--<base target="_top">--><base href="." target="_top">
	<script type="text/javascript" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/jquery.2.2.4.min.js.下载"></script><script type="text/javascript">//<![CDATA[
if (typeof jQuery == 'undefined') {
    document.write(unescape("%3Cscript src='%2fscript%2fJS%2fjquery-2.2.4.min.js' type='text/javascript' %3E%3C/script%3E"));
}//]]></script><script src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/jquery-2.2.4.min.js.下载" type="text/javascript"></script>
<script type="text/javascript">//<![CDATA[
function defrm () { /* thanks twitter */ document.write = ''; window.top.location = window.self.location;  setTimeout(function() { document.body.innerHTML = ''; }, 0);  window.self.onload = function(evt) { document.body.innerHTML = ''; }; }if (window.top !== window.self) {  try {  if (window.top.location.host) { /* will throw */ } else { defrm(); /* chrome */ }  } catch (ex) { defrm(); /* everyone else */ } }if (typeof(DemoUrl)!='undefined')   document.write(unescape('%3Cme')+'ta http'+'-equiv="re'+'fresh"                  con'+'tent="1;url='+DemoUrl+unescape('"%3CE'));

//]]>
</script>

	




<script type="text/javascript">
	var _gaq = _gaq || [];
	_gaq.push(['_setAccount', 'UA-1735123-1']);
	_gaq.push(['_trackPageview']);
	_gaq.push(['_setDomainName', 'www.codeproject.com']);
	_gaq.push(['_setSessionTimeout', '1200']); 

	(function () {
		var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
		ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
		(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
	})(); 
</script><script type="text/javascript" async="" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/ga.js.下载"></script>


</head>	

<body class="chrome chrome67 touch">

<a class="access-link" href="https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling?display=Print#Main"><img alt="Click here to Skip to main content" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/t.gif"></a>





<div class="page-background">

	
	

	

	
    <div id="ctl00_STM" class="site-top-menu fluid">
        <div class="main-content">
            
        </div>
    </div>

	
    <div id="ctl00_SH" class="site-header fluid">
        <div class="main-content">
            <div class="logo"><a href="https://www.codeproject.com/"><img id="ctl00_Logo" tabindex="1" title="CodeProject" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/logo250x135.gif" alt="Home" style="height:135px;width:250px;border-width:0px;"></a></div>
            <div class="promo"></div>
        </div>
    </div>

	<a href="https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling?display=Print#Main"><img alt="Click here to Skip to main content" class="access-link" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/t.gif"></a>

	
			
	

	<div id="A" class="container-content-wrap fluid print"> 
	<div class="container-content">


		<div class="clearfix">
			<div class="container-breadcrumb float-left ">
				<div><a href="https://www.codeproject.com/script/Content/SiteMap.aspx">Articles</a> » <a href="https://www.codeproject.com/Chapters/8/Platforms-Frameworks-Libraries.aspx">Platforms, Frameworks &amp; Libraries</a> » <a href="https://www.codeproject.com/KB/vista/">Windows API</a> » <a href="https://www.codeproject.com/KB/vista/#General">General</a></div>
			</div>

            <div class="float-left">
				
			</div>

			<div class="edit-links float-right">
				
			</div>

			<div class="article-nav float-right">
                
				
			</div>
		</div>

		<table class="extended container-article-parts" cellpadding="0" cellspacing="0">
        <tbody><tr valign="top">
		<td width="117px" class="article-wing-left">

			

		</td>
		<td>
			
			<div id="AT" class="container-article  fluid tight"> 

				<div class="article">

					<form name="aspnetForm" method="post" action="https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling?display=Print" id="aspnetForm" style="margin:0;padding:0">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="InrsEEF/SYOzIVAAVBWHJCfnkzFA0LQXRkBUhqYsKPKMUVQYJO3GKy8rAyumZgUlUMIJlYB1KPgK2hPHFWY8S/a2wI9hUo3HQasSiZawVKHoswnP19nqhavCb0RDJ2X0LS1L1lREdBn6oNG5ntJrcBmahUXmBGL28SOy4yh2EGHxZG6WZvUH1aWwrrpkfEAJ/OIyN+MoukvFEKUz0mytxWo6bF47BZ4q77rLgRmW0rGc4Zgur8F8xjInk+T5IVdJ4PANUSnC+V+s6IkF+O6C6MrL/OV7WPj2/mOPHYeVhQZIjVeiLiEOF0hSO7cho+Metcb544bezoYyzKnRNXjQ83HmLDen3MKEv4YGKoDHuDrZHkHRBGZDSfyNNGBXtEOUIF9+EmmsZi+nmKPyfsO+x9Isodfogtpg1K0NexziASpIIfHeURyoUlV2rJQFHSR4VLooSLnXK/CAtL4FDZTgv3Kh3enTVWluqw7e0CLwPmriIzLh69e9QYDBxVNYFC1mevPomEEQTxaOyTee9vkp1ehzcPVh/sy3hHUwAoVzcKAGYCXfviSgD6KBZDILUGPenD6BPHkRMVH/HIvm3zagzNqV6IIpq/AbOzYHY40+x/5K0abpMFSYR2ylLt1n52Y96io32QW42Jio5h1HfUnRUN6B3NTIlNJQvgF88klZHQcXhIn37mqXTNAjaWdoL2TnWYbGi58hadh64eNMZ5WPxMSH9FGztsF4oRJh/dOKIGwoj1tm7JlARF1TkGfcMmf4f1yWUwCeRZfOTnF5Ewmq3C8u4nQm2BJemNrO1mcGUaiRODHia0Mm9hmNfI2rO2NyLR53kkg9EKtF/buqRDNNa52QFbwrgGuNLyU3BW7MEM6Fz7f9W4O5KnVEzy0uaqiEdkv9TRxd+LazRIss49EKARdBVNuCJo8WGWZ9wUTSW5uHwEDzIMajOaNL0oIX1C7M37ppRswRPR+LmCaAoH+ZfqS9TmE6JAvx/hTYUIJnEWJzSr7xVSAK+EDmkjFa5b2SXAd0ItVbnjaMhvv3NJny3NX/kCyRlgYmjhHIVM6JVQojnrnjzdbYzqjVA9NoJzhOw5J8Tq3GC4NEaqW8VCcx/c8MFUbJsHNxaaErn1PbL8SopHPX4sUy4xCv4OdVgvZyDGqT02c+gmRx4fxZXVPvN098AjnsLOpdTNimJQVAFfflh7DMbVhmk08VT9Ce5Je/q/2FQIcKiL5u1dicKw0JdmqfCtNTN+J9NPBx4xEwfhKYopI9we/KYLNxbtvqV1FQ85p0Eb6kXmheS3wTe8S+fUXxNfdCLkrekJuqH/Gw97lXfixMGhXFxBqC/sh2ARFVONsKVbYW7Evoy2ROglm1oChEYROY7jmQ+5Ak4E6JbkHnpX+VhVqX7SWe/tCt7eH4HecNONOicvc1VWGoOB5OpCasiHTfT88l4F9qlaEjPMMqHih0FBycewwRn3o9B5WDqvPK1gRusUWRIF+mZUh3qGDi/uFtz/EVLdUSfAO2Z4IR+mXK+1+Ar+lbyOxJDQI2rpI2cJxNaAk/+5DPNsro7lFA54tiFkqjhaQQqb4685lS+aXvk6tg7Ttt1yjnPA/9r+K0YRIKZ0VY0XPJgz7OP0/bLq5VuqTTmH3sL/mhQg7kNi346xd2SO2pklsmQGrW8vmPSFbJWq3TacM49zzD2GqOz5toaPpZud/Z+eSbsif68MPxiYiBJTWEn9R1kbKNoy/+u3X3MuFZ3fRxpmLjgxx3LZwMv7LGRU4YobcpVVEG8OE8Ov6HNhIqKS+r0yE3qU7MIBSZ1X2LVKw6Pd0ONY96WWpm9ncz4MIBrsXABkdbhqZYbNwKTbo3Y3Kml7oElqOyCZRhW4Ajf0Np6YBucFAmxmra0Fe4vqlyG4AcLS4SlfH9+5IyPh8e2oq9GREjgkI5pD+e41fB5VvGZxlRb2HcFC57EpIVloVsa0gZTQL3qSEv829C7WzCkyFJxoDBB/Ev9h9/oR9T4gjoomfvlbYqDp7lWXagoBcvFDU4ybH1QmMyKHU9uJkFS6v3Wl/5AesjcjH8x2usy11o5GbqMbzRx4uaWkH5l1vDNsm6pTEGQckTfTQ2nc351Zt13maAbKwIMoLnPBeFWs4JhcjeTbI6exVwIsSW7C0GY8O53nNRao8LKqF2jUhvtO+MwGDSqNKjPDIVqBmFrCLtNMT+nBAdVKaZigSX7iHxfugmpbonCRnGpQUIK7nJTfsvYcNie9s/fbqKk+HDY79Z1iiO2Q4Aenij2w0kCWmMi9hLQvlo0yMpeD+2cssxc78w4bf5isuiBk1v5XMwhUYMPeDBKPauVmNs5jMfJnEUZBwe6b1e/2KtXmKv0e6Ln3duXB75O+7PaQ==">
</div>

<div>

	<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="10C1FD69">
</div>

					
					 
					<div class="header">
					    <a name="Main"></a>

					    
					    <a name="_articleTop" id="_articleTop"></a>
					    <div class="title">
					        <h1 id="ctl00_ArticleTitle">Custom Controls in Win32 API: Scrolling</h1>
					    </div>

                        <div style="height:34px">
					        
					        <div class="entry float-left">

                                <img src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/58de2d085c17e28b8951315245dd408c.jpeg" id="ctl00_avatar" class="float-left avatar">

                                <div class="float-left">
                                    <div>
						                <span class="author"><a href="https://www.codeproject.com/script/Membership/View.aspx?mid=5807385" rel="author">Martin Mitáš</a></span>, 
						                <span class="date" title="Date last updated">
							            27 Feb 2018</span>
			                        </div>

                                    
						            
    						        	

                                </div>

    	                        	
                                <div class="float-left" style="margin: 25px 0 0 10px;">
	                                
	                            </div>
                         	    	
    
					        </div>

                            

						    <div id="ctl00_AdManagerWrap" class="float-right align-center">
                                
						    </div>

                        </div>

                        <div id="ctl00_description" class="summary">How to support scrolling within your controls.</div><span id="ctl00_thumbnailUrl" class="date" content="https://www.codeproject.com/KB/vista/1042516/Thumbnail.png"></span>			

                    </div>
                    
					
					

					

					
					
					

						
					

					

						
						<div id="contentdiv" class="text">
						



<ul class="download">
	<li><a href="https://www.codeproject.com/KB/vista/1042516/ScrollDemo1.zip">Simple scrolling example - 174.2 KB</a></li>	<li><a href="https://www.codeproject.com/KB/vista/1042516/ScrollDemo2.zip">Advanced scrolling example - 186.2 KB</a></li></ul>

<h2>Articles in this series</h2>

<ul>
	<li><a href="http://www.codeproject.com/Articles/559385/Custom-Controls-in-Win32-API-The-Basics">Custom Controls in Win32 API: The Basics</a></li>	<li><a href="http://www.codeproject.com/Articles/617212/Custom-Controls-in-Win32-API-The-Painting">Custom Controls in Win32 API: The Painting</a></li>	<li><a href="http://www.codeproject.com/Articles/620045/Custom-Controls-in-Win32-API-Visual-Styles">Custom Controls in Win32 API: Visual Styles</a></li>	<li><a href="http://www.codeproject.com/Articles/624495/Custom-Controls-in-Win32-API-Standard-Messages">Custom Controls in Win32 API: Standard Messages</a></li>	<li><a href="http://www.codeproject.com/Articles/646482/Custom-Controls-in-Win-API-Control-Customization">Custom Controls in Win32 API: Control Customization</a></li>	<li><a href="http://www.codeproject.com/Articles/744603/Custom-Controls-in-Win-API-Encapsulation-of-Customized-Controls">Custom Controls in Win32 API: Encapsulation of Customized Controls</a></li>	<li>Custom Controls in Win32 API: Scrolling</li></ul>

<h2>Introduction</h2>

<p>Many controls are designed to present some dynamic contents, or larger amount of data which cannot fit into the control or a dialog. In such cases, the controls often need to be equipped with scrollbars so that user can navigate within the data. Standard controls like list view or tree view can serve as prime examples of this approach.</p>

<p>Windows API directly supports scrollbars within every single window and in the todays article we are going to show how to take advantage of it.</p>

<p>Note that in <tt>COMCTL32.DLL</tt>, Windows also offers a standalone scrollbar control but we won't cover it here. Once you understand the implicit scrollbar support we will talk about, usage of the standlone scrollbar control becomes very simple and straightforward.</p>

<h2>Non-Client Area</h2>

<p>Before we start talking about the scrollbars, we need to know about the concept of non-client area. In Windows, every window (<code>HWND</code>) distinguishes its client and non-client area. The client area (usually) covers most (or all) of the window on the screen, and actually more or less all fundamental contents of controls is painted in it.</p>

<p>Non-client area is an optional margin around the client area which can be used for some auxiliary content. For top-level windows, this involves the window border, the window caption with the window title and buttons for minimizing, maximizing and closing the window, the menubar and a border around the window.</p>

<p>Also child windows can and quite often take use of the non-client area. In most cases, a simple border and possibly (if needed) the scrollbars are painted in it. Usually (i.e. unless overridden) if the control has a style <code>WS_BORDER</code> or extended style <code>WS_EX_CLIENTEDGE</code>, the control gets the border.</p>

<p>Similarly, if the control decides it needs a scrollbar, Windows reserves more space in the non-client area on top and/or bottom side of the control for the scrollbars.</p>

<p>This behavior for the border and scrollbars is implemented in the function <code>DefWindowProc()</code> which handles many messages:</p>

<ul>
	<li><code>WM_NCCALCSIZE</code> determines dimensions of the non-client area. The default implementation looks for example at the style <code>WS_BORDER</code>, extended style <code>WS_EX_CLIENTEDGE</code> and state of the scrollbars to do so.</li>	<li><code>WM_NCxxxx</code> counterparts of various mouse messages together with <code>WM_NCHITTEST</code> handle interactivity of the non-client area. In the case of child control this typically involves reaction on the scrollbar buttons and <code>DefWindowProc()</code> does this for us.</li>	<li><code>WM_NCPAINT</code> is called to paint the non-client area. Again, handler of the message in <code>DefWindowProc()</code> knows how to paint the border and the scrollbars.</li></ul>

<p>All of this standard behavior can be overridden if you handle these messages in your window procedure, but that's not the what I want to talk about today. For the purpose of scrolling we can stick with the default behavior offered by <code>DefWindowProc()</code>.</p>

<h2>Setting Up the Scrollbars</h2>

<p>Each <code>HWND</code> remembers two sets of few integer values which describe state of both the horizontal and vertical scrollbars. The set corresponds to the members of structure <code>SCROLLINFO</code> (except the auxiliary <code>cbSize</code> and <code>fMask</code>):</p>

<pre lang="C"><span class="code-keyword">typedef</span> <span class="code-keyword">struct</span> tagSCROLLINFO
{
    UINT    cbSize;
    UINT    fMask;
    <span class="code-keyword">int</span>     nMin;
    <span class="code-keyword">int</span>     nMax;
    UINT    nPage;
    <span class="code-keyword">int</span>     nPos;
    <span class="code-keyword">int</span>     nTrackPos;
}   SCROLLINFO, FAR *LPSCROLLINFO;</pre>

<div style="float: right; padding: 2em"><img src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/scrollbar.png">
<div align="center" class="Caption">Anatomy of the scrollbar</div>
</div>

<p>Note you are free to choose any units for the scrolling you like. Use whatever suits logic of your control the best. The values may be pixels, count of rows (or columns), amount of lines of text or whatever.</p>

<p>The values <code>nMin</code> and <code>nMax</code> determine range of the scrollbars, i.e. minimal and maximal positions corresponding to the scrollbar's thumb moved to top (or left) and bottom (or right) position. In most cases <code>nMin</code> can be just always set to zero and control updates just the upper limit <code>nMax</code>.</p>

<p>The value <code>nPage</code> describes portion of the contents between <code>nMin</code> and <code>nMax</code> which can be displayed in the control given the size of its client area. Windows also visualizes this value in proportional size of the scrollbars thumb.</p>

<p>The value <code>nPos</code> determines the current scrollbar position, so the control is supposed to paint the corresponding portion of its content.</p>

<p>The value <code>nTrackPos</code> is position of the thumb when it is currently being dragged to a new position. This value is read-only and cannot be directly changed programmatically.</p>

<p>Controls which want to support the scrolling can update these values with function <code>SetScrollInfo()</code>, or alternatively with some less general function like <code>SetScrollPos()</code> or <code>SetScrollRange()</code> which can update only subsets of the values.</p>

<p>Remember that all these setter functions implicitly ensure that <code>nPos</code> and <code>nPage</code> are always in the allowed ranges so that the following conditions hold all the time:</p>

<ul>
	<li><code>0 &lt;= nPage &lt; nMax - nMin</code></li>	<li><code>nMin &lt;= nPos &lt;= nMax - nPage</code></li></ul>

<p>If it is logically impossible to fulfill the conditions, e.g. because <code>nPage &gt; nMax - nMin</code>, then no scrolling is needed, Windows resets <code>nPos</code> to zero and hides the scrollbar (which results to the resizing of the client area and <code>WM_SIZE</code> message).</p>

<p>This means that you, as a caller of those function, do not need to care too much about the boundary cases. If, for example, you handle reaction to the key <tt>[PAGE UP]</tt> as scrolling a page up, you simply may do something like this:</p>

<pre lang="C"><span class="code-comment">//</span><span class="code-comment"> Get current nPos and nPage:</span>
<span class="code-keyword">int</span> scrollbarId = (isVertical ? SB_VERT : SB_HORZ);
SCROLLINFO si;
si.cbSize = <span class="code-keyword">sizeof</span>(SCROLLINFO);
si.fMask = SIF_POS | SIF_PAGE;
GetScrollInfo(hwnd, scrollbarId, &amp;si);

<span class="code-comment">//</span><span class="code-comment"> Set new position one page up.</span>
<span class="code-comment">//</span><span class="code-comment"> Note we do not care whether we underflow below nMin as SetScrollInfo()</span>
<span class="code-comment">//</span><span class="code-comment"> does that for us automatically.</span>
si.fMask = SIF_POS;
si.nPos -= si.nPage;
SetScrollInfo(hwnd, scrollbarId, &amp;si);

<span class="code-comment">//</span><span class="code-comment"> If we need to count with nPos below, we may need to ask again for the fixed</span>
<span class="code-comment">//</span><span class="code-comment"> up value of it:</span>
GetScrollInfo(hwnd, scrollbarId, &amp;si);</pre>

<p>Typically, controls supporting the scrolling need to update the state of the scrollbars in the following situations:</p>

<ul>
	<li>Control has to update <code>nMin</code> and/or <code>nMax</code> when amount or size of visible contents of the control changes. E.g. in a case of a control similar to a standard tree-view whenever new (visible) items are added or removed, or when an item is expanded or collapsed.</li>	<li>Control has to update <code>nPage</code> when size of the client area changes (i.e. when handling <code>WM_SIZE</code>) so that it reflects amount of content which can fit in it.</li>	<li>Control has to update <code>nPos</code> when it responds to the scrolling event as described by <code>WM_VSCROLL</code> or <code>WM_HSCROLL</code>. We will cover this more thoroughly later in this article.</li></ul>

<p>Some other situations when the scrollbar state needs to be updated can be when dimension of some elements of the contents changes, e.g. when control starts to use different font which has different size. Often, the amount of related work depends how smartly you choose the scrolling unit: Consider a tree-view control and vertical scrolling: If it uses pixels as the scrolling units, then change of item height (e.g. as a result of <code>WM_SETFONT</code>) has to be reflected by recomputing of the scrollbar's state, but if you use rows as the scrolling units, then it does not.</p>

<h2>Little Gotcha</h2>

<p>When your control supports both horizontal and vertical scrollbars, there is a little trap. Remember that when setting up e.g. a vertical scrollbar, and the values change so that the scrollbar gets visible or gets hidden, the size of its client area changes.</p>

<p>This change in client area size can result also in the need to update state of the other scrollbar.</p>

<p>Consider the following code demonstrating the issue:</p>

<pre lang="C"><span class="code-keyword">static</span> <span class="code-keyword">void</span>
CustomOnWmSize(HWND hWnd, UINT uWidth, UINT uHeight)
{
    SCROLLINFO si;

    si.cbSize = <span class="code-keyword">sizeof</span>(SCROLLINFO);
    si.fMask = SIF_PAGE;

    si.nPage = uWidth;
    SetScrollInfo(hWnd, SB_HORZ, &amp;si, FALSE);

    <span class="code-comment">//</span><span class="code-comment"> BUG: The SetScrollInfo() above can result in yet another resizing of</span>
    <span class="code-comment">//</span><span class="code-comment"> the client area and recursive WM_SIZE message if the new page size</span>
    <span class="code-comment">//</span><span class="code-comment"> causes the scrollbar gets visible or gets hidden, and hence causes change</span>
    <span class="code-comment">//</span><span class="code-comment"> of the non-client area size.</span>
    <span class="code-comment">//</span><span class="code-comment"></span>
    <span class="code-comment">//</span><span class="code-comment"> But after the recursive call returns the next call to SetScrollInfo()</span>
    <span class="code-comment">//</span><span class="code-comment"> may break the vertical scrollbar with possibly not-longer-valid value</span>
    <span class="code-comment">//</span><span class="code-comment"> of uHeight.</span>

    si.nPage = uHeight;
    SetScrollInfo(hWnd, SB_VERT, &amp;si, TRUE);
}

<span class="code-keyword">static</span> LRESULT
CustomProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    <span class="code-keyword">switch</span>(uMsg) {
        ...

        <span class="code-keyword">case</span> WM_SIZE:
            CustomOnWmSize(hWnd, LOWORD(lParam), HIWORD(lParam));
            <span class="code-keyword">return</span> <span class="code-digit">0</span>;

        ...
    }
}</pre>

<p>Once you understand the issue, the fix is simple:</p>

<pre lang="C"><span class="code-keyword">static</span> <span class="code-keyword">void</span>
CustomOnWmSize(HWND hWnd, UINT uWidth, UINT uHeight)
{
    SCROLLINFO si;

    si.cbSize = <span class="code-keyword">sizeof</span>(SCROLLINFO);
    si.fMask = SIF_PAGE;

    si.nPage = uWidth;
    SetScrollInfo(hWnd, SB_HORZ, &amp;si, FALSE);

    <span class="code-comment">//</span><span class="code-comment"> FIX: Make sure uHeight has the right value:</span>
    {
        RECT rc;
        GetClientRect(hWnd, &amp;rc);
        uHeight = rc.bottom - rc.top;
    }

    si.nPage = uHeight;
    SetScrollInfo(hWnd, SB_VERT, &amp;si, TRUE);
}</pre>

<h2>Handling WM_VSCROLL and WM_HSCROLL</h2>

<p>When the scrollbar is visible (i.e. whenever <code>nMax - nMin &gt; nPage</code>), and user interacts with it e.g. by clicking on a scrolling arrow button or by dragging the thumb, the window procedure gets corresponding non-client mouse messages. When passed to <code>DefWindowProc()</code>, they are translated to messages <code>WM_VSCROLL</code> (for the vertical scrollbar) and <code>WM_HSCROLL</code> (for the horizontal scrollbar).</p>

<p>The control's window procedure is supposed to handle them as follows:</p>

<ol>
	<li>Analyze the action requested by the user.</li>	<li>Update <code>nPos</code> accordingly.</li>	<li>Refresh client area so that the control presents corresponding portion of the contents.</li></ol>

<p>Hence the typical handler code may look as follows:</p>

<pre lang="C"><span class="code-keyword">static</span> <span class="code-keyword">void</span>
CustomHandleVScroll(HWND hwnd, <span class="code-keyword">int</span> iAction)
{
    <span class="code-keyword">int</span> nPos;
    <span class="code-keyword">int</span> nOldPos;
    SCROLLINFO si;

    <span class="code-comment">//</span><span class="code-comment"> Get current scrollbar state:</span>
    si.cbSize = <span class="code-keyword">sizeof</span>(SCROLLINFO);
    si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;
    GetScrollInfo(pData-&gt;hwnd, SB_VERT, &amp;si);

    nOldPos = si.nPos;

    <span class="code-comment">//</span><span class="code-comment"> Compute new nPos.</span>
    <span class="code-comment">//</span><span class="code-comment"> Note we do not care where nPos falls between nMin and nMax. See below.</span>
    <span class="code-keyword">switch</span> (iAction) {
    <span class="code-keyword">case</span> SB_TOP:            nPos = si.nMin; <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> SB_BOTTOM:         nPos = si.nMax; <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> SB_LINEUP:         nPos = si.nPos - <span class="code-digit">1</span>; <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> SB_LINEDOWN:       nPos = si.nPos + <span class="code-digit">1</span>; <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> SB_PAGEUP:         nPos = si.nPos - CustomLogicalPage(si.nPage); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> SB_PAGEDOWN:       nPos = si.nPos + CustomLogicalPage(si.nPage); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> SB_THUMBTRACK:     nPos = si.nTrackPos; <span class="code-keyword">break</span>;
    <span class="code-keyword">default</span>:
    <span class="code-keyword">case</span> SB_THUMBPOSITION:  nPos = si.nPos; <span class="code-keyword">break</span>;
    }

    <span class="code-comment">//</span><span class="code-comment"> Update the scrollbar state (nPos) and repaint it. The function ensures</span>
    <span class="code-comment">//</span><span class="code-comment"> the nPos does not fall out of the allowed range between nMin and nMax</span>
    <span class="code-comment">//</span><span class="code-comment"> hence we ask for the corrected nPos again.</span>
    SetScrollPos(hwnd, SB_VERT, nPos, TRUE);
    nPos = GetScrollPos(hwnd, SB_VERT);

    <span class="code-comment">//</span><span class="code-comment"> Refresh the control (repaint it to reflect the new nPos). Note we</span>
    <span class="code-comment">//</span><span class="code-comment"> here multiply with some unspecified scrolling unit which specifies</span>
    <span class="code-comment">//</span><span class="code-comment"> amount of pixels corresponding to the 1 scrolling unit.</span>
    <span class="code-comment">//</span><span class="code-comment"> We will discuss ScrollWindowEx() more later in the article.</span>
    ScrollWindowEx(hwnd, <span class="code-digit">0</span>, (nOldPos - nPos) * scrollUnit
                   NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
}

<span class="code-keyword">static</span> LRESULT CALLBACK
CustomProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    <span class="code-keyword">switch</span>(uMsg) {
        ...

        <span class="code-keyword">case</span> WM_VSCROLL:
            <span class="code-comment">//</span><span class="code-comment"> LOWORD(wParam) determines the desired scrolling action.</span>
            CustomHandleVScroll(hwnd, LOWORD(wParam));
            <span class="code-keyword">return</span> <span class="code-digit">0</span>;

        ...
    }

    <span class="code-keyword">return</span> DefWindowProc(hwnd, uMsg, wParam, lParam);
}</pre>

<p>For <code>WM_HSCROLL</code>, the code would be very similar.</p>

<h2>Updating the Client Area</h2>

<p>In the code snippet above, we have used the function <code>ScrollWindowEx()</code>. Lets now take a closer look on it.</p>

<p>Painting the client area is task the control usually performs in the handler of message <code>WM_PAINT</code>. In case of control which supports scrolling, the function has to take the current <code>nPos</code> value into consideration. (Or actually two values, <code>nPosHoriz</code> and <code>nPosVert</code> if the control supports scrolling in both directions.)</p>

<p>Typically this means that the control contents is painted with vertical and horizontal offsets, <code>-(nPosVert * uScrollUnitHeight)</code> and <code>-(nPosHoriz * uScrollUnitWidth)</code>, so that the control presents content further to the bottom and right when the scrollbars are not in the minimal positions. (<code>uScrollUnitWidth</code> and <code>uScrollUnitHeight</code> determine width and height of the scrolling units in pixels.)</p>

<p>When application changes state of the scrollbar (i.e. the range, the position, or even the page size), it usually needs to repaint itself. It could just invalidate its client area and let <code>WM_PAINT</code> paint everything from scratch.</p>

<p>Or it can do something much smarter. In most cases when scrolling, there is often quite a lot of correctly painted stuff already available on the screen. It's just painted on bad position which corresponds to the old value of <code>nPos</code>, right?</p>

<p>The solution is to simply move all the still valid contents from the old position to the new one, and only invalidate portion of the client area which really needs to be repainted from scratch, i.e. only the area which roughly corresponds to the horizontal or vertical stripe which moves into the visible view-port from "behind the corner" during the scrolling operation.</p>

<p>And that is exactly what the function <code>ScrollWindowEx()</code> is good for. You tell it a rectangle, you tell it a horizontal and vertical offsets (difference between old and new <code>nPos</code>) in pixels, and it does all the magic. It actually copies/moves some graphic memory from one place to another to reuse as much as possible of the old contents, and it only invalidates those portions of the rectangle which really need to be repainted. Assuming the handler of <code>WM_PAINT</code> is implemented as it should and repaints only the dirty rectangle (refer to <a href="http://www.codeproject.com/Articles/617212/Custom-Controls-in-Win32-API-The-Painting">our 2nd part of this series about painting</a>), it will then have much less work to do.</p>

<h2>Scrolling with Keyboard</h2>

<p>In many cases it's useful to scroll by appropriate keys on a keyboard. Assuming for example that arrow keys, <tt>[HOME]</tt>, <tt>[END]</tt>, <tt>[PAGE DOWN]</tt> and <tt>[PAGE UP]</tt> should translate directly to the scrolling commands, the code can be very simple:</p>

<pre lang="C"><span class="code-keyword">static</span> <span class="code-keyword">void</span>
CustomHandleKeyDown(CustomData* pData, UINT vkCode)
{
    <span class="code-keyword">switch</span> (vkCode) {
    <span class="code-keyword">case</span> VK_HOME:   CustomHandleVScroll(pData, SB_TOP); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> VK_END:    CustomHandleVScroll(pData, SB_BOTTOM); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> VK_UP:     CustomHandleVScroll(pData, SB_LINEUP); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> VK_DOWN:   CustomHandleVScroll(pData, SB_LINEDOWN); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> VK_PRIOR:  CustomHandleVScroll(pData, SB_PAGEUP); <span class="code-keyword">break</span>;
    <span class="code-keyword">case</span> VK_NEXT:   CustomHandleVScroll(pData, SB_PAGEDOWN); <span class="code-keyword">break</span>;
    }
}

<span class="code-keyword">static</span> LRESULT CALLBACK
CustomProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    <span class="code-keyword">switch</span>(uMsg) {
        ...

        <span class="code-keyword">case</span> WM_KEYDOWN:
            CustomHandleKeyDown(pData, wParam);
            <span class="code-keyword">return</span> <span class="code-digit">0</span>;

        ...
    }

    <span class="code-keyword">return</span> DefWindowProc(hwnd, uMsg, wParam, lParam);
}</pre>

<h2>Scrolling with Mouse Wheel</h2>

<p>Adding support for scrolling with a mouse wheel is somewhat more interesting. The main reason why it is not that simple is diversity of available hardware. The mouse wheel in many cases is not really a wheel, and often there is no mouse at all. Consider for example modern trackpads which may map some finger gestures to a virtual mouse wheel.</p>

<p>Even among mouses, there are vast differences. As you should know, computers work mainly with numbers. And hence a scrolling the mouse wheel translates to some number which we may call "delta". Depending on the hardware, its driver, system configuration and position of planets in the Solar system, the same action with the mouse wheel can sometimes result in a larger delta coming at once, or a sequence of smaller deltas coming in short succession.</p>

<p>On Windows, the delta propagates as a parameter of the message <code>WM_MOUSEWHEEL</code> for vertical wheel, or <code>WM_MOUSEHWHEEL</code> for horizontal one: It is stored as the high word of <code>WPARAM</code>.</p>

<p>So, to handle these messages, application (or control in our case) has to accumulate the delta until it reaches some threshold value meaning "scroll one line down" (or up; or to left or right for horizontal scrolling).</p>

<p>Furthermore, the control should respect sensitivity of the wheel as configured in the system. On Windows, this settings can be retrieved with <code>SystemParametersInfo(SPI_GETWHEELSCROLLLINES)</code> for vertical wheel and <code>SystemParametersInfo(SPI_GETWHEELSCROLLCHARS)</code> for horizontal one. Both values correspond to the amount of vertical or horizontal scrolling units the control should scroll when the accumulated delta value reaches the value defined with macro <code>WHEEL_DELTA</code> (120).</p>

<p>The above may look quite difficult, but it's not that bad. Furthermore we may actually implement it just once: Windows supports only one mouse pointer and that implies there is never more then one vertical wheel and one horizontal wheel. Therefore we can use global variables for the accumulated values and one wrapping function dealing with them instead of bloating per-control data structures and reimplementing it in each window procedure.</p>

<p>The code of such function may look as follows:</p>

<pre lang="C"><span class="code-comment">//</span><span class="code-comment"> Lock protecting the static variables. Note you have to initialize the</span>
<span class="code-comment">//</span><span class="code-comment"> critical section before calling the function WheelScrollLines() below</span>
<span class="code-comment">//</span><span class="code-comment"> for the first time.</span>
<span class="code-keyword">static</span> CRITICAL_SECTION csWheelLock;


<span class="code-comment">//</span><span class="code-comment"> Helper function for calculation of scrolling lines for provided mouse wheel</span>
<span class="code-comment">//</span><span class="code-comment"> delta value. This function is quite generic and can be used/shared among</span>
<span class="code-comment">//</span><span class="code-comment"> many controls.</span>
<span class="code-keyword">int</span>
WheelScrollLines(HWND hwnd, <span class="code-keyword">int</span> iDelta, UINT nPage, BOOL isVertical)
{
    <span class="code-comment">//</span><span class="code-comment"> We accumulate the wheel_delta until there is enough to scroll for</span>
    <span class="code-comment">//</span><span class="code-comment"> at least a single line. This improves the feel for strange values</span>
    <span class="code-comment">//</span><span class="code-comment"> of SPI_GETWHEELSCROLLLINES and for some mouses.</span>
    <span class="code-keyword">static</span> HWND hwndCurrent = NULL;         <span class="code-comment">//</span><span class="code-comment"> HWND we accumulate the delta for.</span>
    <span class="code-keyword">static</span> <span class="code-keyword">int</span> iAccumulator[<span class="code-digit">2</span>] = { <span class="code-digit">0</span>, <span class="code-digit">0</span> };  <span class="code-comment">//</span><span class="code-comment"> The accumulated value (vert. and horiz.).</span>
    <span class="code-keyword">static</span> DWORD dwLastActivity[<span class="code-digit">2</span>] = { <span class="code-digit">0</span>, <span class="code-digit">0</span> };

    UINT uSysParam;
    UINT uLinesPerWHEELDELTA;   <span class="code-comment">//</span><span class="code-comment"> Scrolling speed (how much to scroll per WHEEL_DELTA).</span>
    <span class="code-keyword">int</span> iLines;                 <span class="code-comment">//</span><span class="code-comment"> How much to scroll for currently accumulated value.</span>
    <span class="code-keyword">int</span> iDirIndex = (isVertical ? <span class="code-digit">0</span> : <span class="code-digit">1</span>);  <span class="code-comment">//</span><span class="code-comment"> The index into iAccumulator[].</span>
    DWORD dwNow;

    dwNow = GetTickCount();

    <span class="code-comment">//</span><span class="code-comment"> Even when nPage is below one line, we still want to scroll at least a little.</span>
    <span class="code-keyword">if</span> (nPage &lt; <span class="code-digit">1</span>)
        nPage = <span class="code-digit">1</span>;

    <span class="code-comment">//</span><span class="code-comment"> Ask the system for scrolling speed.</span>
    uSysParam = (isVertical ? SPI_GETWHEELSCROLLLINES : SPI_GETWHEELSCROLLCHARS);
    <span class="code-keyword">if</span> (!SystemParametersInfo(uSysParam, <span class="code-digit">0</span>, &amp;uLinesPerWHEELDELTA, <span class="code-digit">0</span>))
        uLinesPerWHEELDELTA = <span class="code-digit">3</span>;  <span class="code-comment">//</span><span class="code-comment"> default when SystemParametersInfo() fails.</span>
    <span class="code-keyword">if</span> (uLinesPerWHEELDELTA == WHEEL_PAGESCROLL) {
        <span class="code-comment">//</span><span class="code-comment"> System tells to scroll over whole pages.</span>
        uLinesPerWHEELDELTA = nPage;
    }
    <span class="code-keyword">if</span> (uLinesPerWHEELDELTA &gt; nPage) {
        <span class="code-comment">//</span><span class="code-comment"> Slow down if page is too small. We don't want to scroll over multiple</span>
        <span class="code-comment">//</span><span class="code-comment"> pages at once.</span>
        uLinesPerWHEELDELTA = nPage;
    }

    EnterCriticalSection(&amp;csWheelLock);

    <span class="code-comment">//</span><span class="code-comment"> In some cases, we do want to reset the accumulated value(s).</span>
    <span class="code-keyword">if</span> (hwnd != hwndCurrent) {
        <span class="code-comment">//</span><span class="code-comment"> Do not carry accumulated values between different HWNDs.</span>
        hwndCurrent = hwnd;
        iAccumulator[<span class="code-digit">0</span>] = <span class="code-digit">0</span>;
        iAccumulator[<span class="code-digit">1</span>] = <span class="code-digit">0</span>;
    } <span class="code-keyword">else</span> <span class="code-keyword">if</span> (dwNow - dwLastActivity[iDirIndex] &gt; GetDoubleClickTime() * <span class="code-digit">2</span>) {
        <span class="code-comment">//</span><span class="code-comment"> Reset the accumulator if there was a long time of wheel inactivity.</span>
        iAccumulator[iDirIndex] = <span class="code-digit">0</span>;
    } <span class="code-keyword">else</span> <span class="code-keyword">if</span> ((iAccumulator[iDirIndex] &gt; <span class="code-digit">0</span>) == (iDelta &lt; <span class="code-digit">0</span>)) {
        <span class="code-comment">//</span><span class="code-comment"> Reset the accumulator if scrolling direction has been reversed.</span>
        iAccumulator[iDirIndex] = <span class="code-digit">0</span>;
    }

    <span class="code-keyword">if</span> (uLinesPerWHEELDELTA &gt; <span class="code-digit">0</span>) {
        <span class="code-comment">//</span><span class="code-comment"> Accumulate the delta.</span>
        iAccumulator[iDirIndex] += iDelta;

        <span class="code-comment">//</span><span class="code-comment"> Compute the lines to scroll.</span>
        iLines = (iAccumulator[iDirIndex] * (<span class="code-keyword">int</span>)uLinesPerWHEELDELTA) / WHEEL_DELTA;

        <span class="code-comment">//</span><span class="code-comment"> Decrease the accumulator for the consumed amount.</span>
        <span class="code-comment">//</span><span class="code-comment"> (Corresponds to the remainder of the integer divide above.)</span>
        iAccumulator[iDirIndex] -= (iLines * WHEEL_DELTA) / (<span class="code-keyword">int</span>)uLinesPerWHEELDELTA;
    } <span class="code-keyword">else</span> {
        <span class="code-comment">//</span><span class="code-comment"> uLinesPerWHEELDELTA == 0, i.e. likely configured to no scrolling</span>
        <span class="code-comment">//</span><span class="code-comment"> with mouse wheel.</span>
        iLines = <span class="code-digit">0</span>;
        iAccumulator[iDirIndex] = <span class="code-digit">0</span>;
    }

    dwLastActivity[iDirIndex] = dwNow;
    LeaveCriticalSection(&amp;csWheelLock);

    <span class="code-comment">//</span><span class="code-comment"> Note that for vertical wheel, Windows provides the delta with opposite</span>
    <span class="code-comment">//</span><span class="code-comment"> sign. Hence the minus.</span>
    <span class="code-keyword">return</span> (isVertical ? -iLines : iLines);
}</pre>

<p>Notes:</p>

<ul>
	<li>First of all, remember the word "line" in the function name and in names of some variables refers rather to general "scrolling units" and not necessarily any real lines in this context. This naming comes from the standard symbolic names for scrolling one scrolling unit up or down (<code>SB_LINEUP</code> and <code>SB_LINEDOWN</code>), or left or right (<code>SB_LINELEFT</code> and <code>SB_LINERIGHT</code>). Sorry for the terminology mess, but "scrolling units" is simply too much typing for someone as lazy as me...</li>	<li>If the function is used in an application where different <code>HWND</code>s are living in multiple threads, it has to be thread-safe to protect the state described by the multiple static variables. Hence the use of <code>CRITICAL_SECTION</code>.</li>	<li>We reset the accumulators in certain situations: When <code>HWND</code> changes, when some longer time expires without the wheel activity or when user starts scrolling to the opposite direction. You may notice the period of inactivity is compared to a time period based on <code>GetDoubleClickTime()</code>. I chose to use that because <a href="http://blogs.msdn.com/b/oldnewthing/archive/2008/04/23/8417521.aspx">the double-click time is used in Windows as a measure how good your reflexes are</a>.</li>	<li>For historic reasons, the delta value for the vertical wheel is provided with opposite sign then most people expect, and in the opposite sense in comparison to the horizontal wheel. To simplify the code we deal with that on the single spot: the last line of the function.</li></ul>

<p>The function <code>WheelScrollLines()</code> is quite generic and reusable. Actually one could even think such function should be implemented in some standard Win32API library. That would at least provide better guaranty that mouse wheels are used consistently by default among applications. But AFAIK it is not, at least not a publicly exported one.</p>

<p>Usage of the function is very straightforward:</p>

<pre lang="C"><span class="code-keyword">static</span> <span class="code-keyword">void</span>
CustomHandleMouseWheel(HWND hwnd, <span class="code-keyword">int</span> iDelta, BOOL isVertical)
{
    SCROLLINFO si;
    <span class="code-keyword">int</span> nPos;
    <span class="code-keyword">int</span> nOldPos;

    si.cbSize = <span class="code-keyword">sizeof</span>(SCROLLINFO);
    si.fMask = SIF_PAGE | SIF_POS;
    GetScrollInfo(hwnd, (isVertical ? SB_VERT : SB_HORZ), &amp;si);

    <span class="code-comment">//</span><span class="code-comment"> Compute how many lines to scroll.</span>
    nOldPos = si.nPos;
    nPos = nOldPos + WheelScrollLines(pData-&gt;hwnd, iDelta, si.nPage, isVertical);

    <span class="code-comment">//</span><span class="code-comment"> Scroll to the desired location.</span>
    SetScrollPos(hwnd, (isVertical ? SB_VERT : SB_HORZ), si.nPos);

    <span class="code-comment">//</span><span class="code-comment"> Update the client area.</span>
    ScrollWindowEx(hwnd, 
                   (isVertical ? <span class="code-digit">0</span> : (nOldPos - nPos) * scrollUnit),
                   (isVertical ? (nOldPos - nPos) * scrollUnit, <span class="code-digit">0</span>),
                   NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
}

<span class="code-keyword">static</span> LRESULT CALLBACK
CustomProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    <span class="code-keyword">switch</span>(uMsg) {
        ...

        <span class="code-keyword">case</span> WM_MOUSEWHEEL:
            CustomHandleMouseWheel(hwnd, HIWORD(wParam), TRUE);
            <span class="code-keyword">return</span> <span class="code-digit">0</span>;

        <span class="code-keyword">case</span> WM_MOUSEHWHEEL:
            CustomHandleMouseWheel(hwnd, HIWORD(wParam), FALSE);
            <span class="code-keyword">return</span> <span class="code-digit">0</span>;

        ...
    }

    <span class="code-keyword">return</span> DefWindowProc(hwnd, uMsg, wParam, lParam);
}</pre>

<h2>Examples for Download</h2>

<p>This time, there are two example projects available for download. You may find links to both of them at the very top of this article.</p>

<p>The simpler allows only vertical scrolling, but otherwise corresponds roughly to all the code provided in the article.</p>

<p align="center"><img alt="Screenshot of the simple demo" height="343" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/demo1.png" width="286"></p>

<div align="center" class="Caption">Screenshot of the simple demo</div>

<p>The 2nd (and more complex) example does scrolling in vertical as well as horizontal direction, it has a dynamically changing contents so that <code>nMin</code> and <code>nMax</code> change throughout lifetime of the control, it presents usage of non-trivial scrolling units and last but not least, it shows more advanced use of the function <code>ScrollWindowEx()</code> which scrolls only part of the window to keep the headers of columns and rows always visible.</p>

<p align="center"><img alt="Screenshot of the more complex demo" height="501" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/demo2.png" width="395"></p>

<div align="center" class="Caption">Screenshot of the more complex demo</div>

<h2>Real World Code</h2>

<p>In this series, it's already tradition to provide also some links to real-life code demonstrating the topic of the article.</p>

<p>To get better insight, you might find very useful to study the (re)implementation of the scrollbar support in <a href="http://www.winehq.com/">Wine</a>. I especially recommend to pay attention to the function <code>SCROLL_SetScrollInfo()</code> which implements core of the <code>SetScrollInfo()</code>:</p>

<ul>
	<li>Scrollbar implementation: <a href="https://github.com/mirrors/wine/blob/master/dlls/user32/scroll.c">https://github.com/mirrors/wine/blob/master/dlls/user32/scroll.c</a></li></ul>

<p>And, of course, some Wine controls using the implicit scrollbars:</p>

<ul>
	<li>(Multiline) edit box: <a href="https://github.com/mirrors/wine/blob/master/dlls/user32/edit.c">https://github.com/mirrors/wine/blob/master/dlls/user32/edit.c</a></li>	<li>List view control: <a href="https://github.com/mirrors/wine/blob/master/dlls/comctl32/listview.c">https://github.com/mirrors/wine/blob/master/dlls/comctl32/listview.c</a></li>	<li>Tree view control: <a href="https://github.com/mirrors/wine/blob/master/dlls/comctl32/treeview.c">https://github.com/mirrors/wine/blob/master/dlls/comctl32/treeview.c</a></li></ul>

<p>Finally, also few <a href="http://mctrl.org/">mCtrl</a> controls using it:</p>

<ul>
	<li>Grid control: <a href="https://github.com/mity/mctrl/blob/master/src/grid.c">https://github.com/mity/mctrl/blob/master/src/grid.c</a></li>	<li>Tree-list view control: <a href="https://github.com/mity/mctrl/blob/master/src/treelist.c">https://github.com/mity/mctrl/blob/master/src/treelist.c</a></li></ul>

<h2>Next Time: More About Non-Client Area</h2>

<p>Today, we discussed how to implement scrolling support in a control. During the journey, we have lightly touched the topic of non-client area as that's where the scrollbars are living. Next time, we will take a look how to customize a little bit the non-client area, and how to paint in it.</p>


						</div>
						

						<div class="float-right" style="margin:20px 0 0 10px;border:1px solid #ccc">
						
						</div>
                        
                        
						
						<h2>License</h2>
						<div id="LicenseTerms"><p>This article, along with any associated source code and files, is licensed under <a href="http://www.codeproject.com/info/cpol10.aspx" rel="license">The Code Project Open License (CPOL)</a></p></div><h2 id="ctl00_AboutHeading">About the Author</h2>
						    

<div class="author-wrapper">

    <div class="pic-wrapper"> 
        <img id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_memberPhoto" class="profile-pic" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/58de2d085c17e28b8951315245dd408c.jpeg" style="border-width:0px;transform:rotate(1deg);">
    </div>

    <div class="container-member">  
        <b><a id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_memberProfileLink" class="author" href="https://www.codeproject.com/Members/Martin-Mitas">Martin Mitáš</a></b>

        <table>
        <tbody><tr>
            <td rowspan="2" nowrap="" valign="middle">
            <div id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowOn" class="follow">
	            
	            
	            <a href="https://www.linkedin.com/profile/view?id=88607525"><img style="vertical-align:middle;border:0;height:24px;width:24px" alt="LinkedIn" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/linkedin-32.png"></a> 
            </div>
            </td>
            <td nowrap="nowrap">
                <div class="company">
                    <span id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_memberJobTitle">Team Leader</span>
	                <span id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_memberCompany"></span>
                </div>
            </td>
            <td rowspan="2" style="padding-left:15px">
                <a id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowMember_FollowBtn" title="Have events related to this Member appear in your timeline" class="button unfollowed align-center invisible" data-enabletooltip="true" data-tooltip="Have the articles and posts from this member appear in your Timeline so you can stay up to date." style="display:none;">Follow<div class="tiny-text" style="margin-top:-3px;">this Member</div></a>
	<a id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowMember_UnfollowBtn" title="Unfollow this Member" class="button followed align-center invisible" data-enabletooltip="true" data-tooltip="Stop following this Member" style="display:none;">Unfollow</a>

            </td>
        </tr>
        <tr>
            <td>
                <span id="ctl00_AboutAuthorRptr_ctl00_AboutAuthor_memberLocation">Czech Republic <img src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/CZ.gif" alt="Czech Republic" width="16px" height="11px"></span>
            </td>
        </tr>
        </tbody></table>
    </div>

    <div class="description">
        No Biography provided

        
    </div>

</div><br>
						
						

						<div class="clearfix"></div>

						<div style="padding-top:8px">
							
						</div>

					

				    
					</form>

				</div>

				
				<div class="bottom-promo"> 
				    
				</div>
				

                

				
				

					<h2>Comments and Discussions</h2>
					
					<p><img alt="Comment" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/NewComment.gif" width="12px" height="16px">
					<b>14 messages</b> have been posted for this article 
					Visit <b><a id="ctl00_ArticleLink" href="https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling">https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling</a></b> to post and view comments on 
					this article, or click <b><a id="ctl00_PrintWithMessage" href="https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling?display=PrintAll">here</a></b> 
					to get a print view with messages.</p>
					
				

			</div>
			
		</td>
		<td width="170px" class="article-wing-right">
			
		</td>
		</tr></tbody></table>

		
		<div class="theme1-background" style="height:2px" id="stickyStop"></div>

		<div class="extended tiny-text">
			<div class="row">
				<div class="float-left">
					<a id="ctl00_PermaLink" href="https://www.codeproject.com/Articles/1042516/Custom-Controls-in-Win-API-Scrolling">Permalink</a> | 
					<a id="ctl00_AdvertiseLink" href="http://developermedia.com/">Advertise </a> |
					<a id="ctl00_PrivacyLink" href="https://www.codeproject.com/info/privacy.aspx">Privacy</a> |
    				<a id="ctl00_CookiePolicyLink" href="https://www.codeproject.com/info/cookies.aspx">Cookies</a> |
                    <a id="ctl00_TermsOfUseLink" href="https://www.codeproject.com/info/TermsOfUse.aspx">Terms of Use</a> |
					<a id="ctl00_Mobile">Mobile</a>
					<br>
								
					
					Web06 |
					2.8.181119.1 |
					Last Updated 26 Feb 2018								
				</div>

                      

				<div class="float-right align-right">
					Article Copyright 2015 by Martin Mitáš<br>Everything else
					Copyright © <a href="mailto:webmaster@codeproject.com">CodeProject</a>, 1999-2018 <br>
				</div>

				

			</div>
		</div>
		

		<br clear="all">
		
			

	</div> 
	</div>
</div>






<script type="text/javascript" src="./Custom Controls in Win32 API_ Scrolling - CodeProject_files/MemberProfilePopup.min.js.下载"></script>
<script type="text/javascript">//<![CDATA[
$(document).ready(function () {
   processCodeBlocks.Initialise('#contentdiv');
   thumbnailer.Initialise('#contentdiv',700,600);
});
cookieconsent.initialise({
                                enabled : true,
                                cookie  : { domain: 'codeproject.com' },
                                palette : {
                                    popup: { background: '#ff9900' },
                                    button: { background: '#f5d948' }
                                },
                                law :  {
                                    showForAllRegions : true, 
                                    countryCode : 'CN' 
                                },
                                theme: 'edgeless',
                                type : 'opt-in',
                                content: {
                                    message: 'Like every other website we use cookies. By using our site you acknowledge that you have read and understand our <a href=\'/info/cookie.aspx\'>Cookie Policy</a>, <a href=\'/info/privacy.aspx\'>Privacy Policy</a>, and our <a href=\'/info/TermsOfUse.aspx\'>Terms of Service</a>.',
                                    href:    'https://www.codeproject.com/info/privacy.aspx'
                                },
                                revokable:true,
                                onStatusChange: function(status) {
                                    $.ajax({
                                		dataType  : 'json',
                                        data: JSON.stringify({ allowCookies : this.hasConsented() }),
                                        url: '/script/Common/webservices/CommonServices.aspx/SetCookieConsent',
                                        cache: false,
                                        type: 'POST',
                                        contentType: 'application/json'
                                    })
                                    console.log(this.hasConsented() ? 'enable cookies' : 'disable cookies');
                                    console.log(this.hasAnswered() ? 'has answered' : 'did not answer');
                                },
                            });
$(document).ready(function() { anchorAnimate();
});
$(document).ready(function(){
    $('#__EVENTVALIDATION').attr('autocomplete', 'off');
});
$(function() {
                        var followServicectl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowMember = new FollowService('/script/follow/webservices/followServices.aspx/', true);
                        followServicectl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowMember.initFollowObjectButtons({
                            item : { 
                                objectTypeId   : 1,
                                objectId       : 5807385
                            },
                            followButtonId   : 'ctl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowMember_FollowBtn',
                            unfollowButtonId : 'ctl00_AboutAuthorRptr_ctl00_AboutAuthor_FollowMember_UnfollowBtn'
                        }, false);
                    });

//]]>
</script>





<div id="MemberProfilePopupDiv" class="raised box" style="display: none; position: absolute;"></div><div id="MemberProfilePopupDiv" class="raised box" style="display: none; position: absolute;"></div><div id="MemberProfilePopupDiv" class="raised box" style="display: none; position: absolute;"></div></body></html>